/**
 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/ecma_string_table.h"

#include "plugins/ecmascript/runtime/ecma_string-inl.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/mem/ecma_string.h"
#include "plugins/ecmascript/runtime/object_factory.h"

namespace ark::ecmascript {
EcmaStringTable::EcmaStringTable(const EcmaVM *vm) : vm_(vm) {}

EcmaString *EcmaStringTable::GetString(const uint8_t *utf8Data, uint32_t utf8Len, bool canBeCompress) const
{
    uint32_t hashCode = EcmaString::ComputeHashcodeUtf8(utf8Data, utf8Len, canBeCompress);
    os::memory::ReadLockHolder holder(tableLock_);
    for (auto it = table_.find(hashCode); it != table_.end(); it++) {
        auto foundedString = it->second;
        if (EcmaString::StringsAreEqualUtf8(foundedString, utf8Data, utf8Len, canBeCompress)) {
            return foundedString;
        }
    }
    return nullptr;
}

EcmaString *EcmaStringTable::GetString(const uint16_t *utf16Data, uint32_t utf16Len) const
{
    uint32_t hashCode = EcmaString::ComputeHashcodeUtf16(const_cast<uint16_t *>(utf16Data), utf16Len);
    os::memory::ReadLockHolder holder(tableLock_);
    for (auto it = table_.find(hashCode); it != table_.end(); it++) {
        auto foundedString = it->second;
        if (EcmaString::StringsAreEqualUtf16(foundedString, utf16Data, utf16Len)) {
            return foundedString;
        }
    }
    return nullptr;
}

EcmaString *EcmaStringTable::GetString(EcmaString *string) const
{
    auto hash = string->GetHashcode();
    os::memory::ReadLockHolder holder(tableLock_);
    for (auto it = table_.find(hash); it != table_.end(); it++) {
        auto foundedString = it->second;
        if (EcmaString::StringsAreEqual(foundedString, string)) {
            return foundedString;
        }
    }
    return nullptr;
}

ObjectHeader *EcmaStringTable::ResolveString(const panda_file::File &pf, panda_file::File::EntityId id)
{
    auto foundStr = pf.GetStringData(id);
    const auto factory = vm_->GetFactory();

    if (UNLIKELY(foundStr.utf16Length == 0)) {
        return *(factory->GetEmptyString());
    }

    return EcmaString::Cast(this->GetOrInternString(foundStr.data, foundStr.utf16Length, foundStr.isAscii));
}

void EcmaStringTable::InternString(EcmaString *string)
{
    if (string->IsInternString()) {
        return;
    }
    os::memory::WriteLockHolder holder(tableLock_);
    table_.insert(std::pair<uint32_t, EcmaString *>(string->GetHashcode(), string));
    string->SetIsInternString();
}

void EcmaStringTable::InternEmptyString(EcmaString *emptyStr)
{
    InternString(emptyStr);
}

EcmaString *EcmaStringTable::GetOrInternString(const uint8_t *utf8Data, uint32_t utf8Len, bool canBeCompress,
                                               ark::SpaceType spaceType)
{
    EcmaString *result = GetString(utf8Data, utf8Len, canBeCompress);
    if (result != nullptr) {
        return result;
    }

    result = EcmaString::CreateFromUtf8(utf8Data, utf8Len, vm_, canBeCompress, spaceType);
    InternString(result);
    return result;
}

EcmaString *EcmaStringTable::GetOrInternString(const uint16_t *utf16Data, uint32_t utf16Len, bool canBeCompress,
                                               ark::SpaceType spaceType)
{
    EcmaString *result = GetString(utf16Data, utf16Len);
    if (result != nullptr) {
        return result;
    }

    result = EcmaString::CreateFromUtf16(utf16Data, utf16Len, vm_, canBeCompress, spaceType);
    InternString(result);
    return result;
}

EcmaString *EcmaStringTable::GetOrInternString(EcmaString *string)
{
    if (string->IsInternString()) {
        return string;
    }

    EcmaString *result = GetString(string);
    if (result != nullptr) {
        return result;
    }
    InternString(string);
    return string;
}

void EcmaStringTable::VisitRoots(const StringTable::StringVisitor &visitor,
                                 [[maybe_unused]] mem::VisitGCRootFlags flags)
{
    os::memory::ReadLockHolder holder(tableLock_);
    for (const auto &v : table_) {
        visitor(v.second);
    }
}

void EcmaStringTable::Sweep(const GCObjectVisitor &visitor)
{
    os::memory::WriteLockHolder holder(tableLock_);
    for (auto it = table_.begin(); it != table_.end();) {
        auto *object = it->second;
        if (object->IsForwarded()) {
            ASSERT(visitor(object) != ObjectStatus::DEAD_OBJECT);
            ObjectHeader *fwdString = ark::mem::GetForwardAddress(object);
            it->second = static_cast<EcmaString *>(fwdString);
            ++it;
            LOG(DEBUG, GC) << "StringTable: forward " << std::hex << object << " -> " << fwdString;
        } else if (visitor(object) == ObjectStatus::DEAD_OBJECT) {
            LOG(DEBUG, GC) << "StringTable: delete string " << std::hex << object
                           << ", val = " << ConvertToPandaString(object);
            table_.erase(it++);
        } else {
            ++it;
        }
    }
}

bool EcmaStringTable::UpdateMoved(const GCRootUpdater &gcRootUpdater)
{
    bool updated = false;
    os::memory::WriteLockHolder holder(tableLock_);
    for (auto it = table_.begin(); it != table_.end();) {
        ObjectHeader *object = it->second;
        if (gcRootUpdater(&object)) {
            it->second = static_cast<EcmaString *>(object);
            LOG(DEBUG, GC) << "StringTable: forwarded " << std::hex << object;
            updated = true;
        }
        ++it;
    }
    return updated;
}

void EcmaStringTable::SweepWeakReference(const WeakRootVisitor &visitor)
{
    os::memory::WriteLockHolder holder(tableLock_);
    for (auto it = table_.begin(); it != table_.end();) {
        auto *object = it->second;
        auto fwd = visitor(object);
        if (fwd == nullptr) {
            LOG(DEBUG, GC) << "StringTable: delete string " << std::hex << object
                           << ", val = " << ConvertToPandaString(object);
            table_.erase(it++);
        } else if (fwd != object) {
            it->second = static_cast<EcmaString *>(fwd);
            ++it;
            LOG(DEBUG, GC) << "StringTable: forward " << std::hex << object << " -> " << fwd;
        } else {
            ++it;
        }
    }
}
}  // namespace ark::ecmascript
